﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Xml;
using BackupAssurance.Library.DBEF;
using BackupAssurance.Library.Events;
using BackupAssurance.Library.Interfaces;
using BackupAssurance.Library.MEF;
using BackupAssurance.Library.ModelsDB;
using BackupAssurance.Library.Constants;
using System.Net.Mail;
using System.Collections.Specialized;


namespace BackupAssurance
{
    /// <summary>
    /// QuickRun is an intermeddiary class implementing all the functionality of the BackupAssurance service.
    /// This class would not have been required, if it was not to help with testing.  It allows for local execution of the 
    /// code, without being installed a service.  It is called by both BackupAssurance and ProjectStartsHere (test console)
    /// The OnStop method is called both by the test project and the windows service when it needs the application to Stop.
    /// The OnStart method is called both by the test project and the windows service when it needs the application to Start.
    /// On initialization, this class composes all the MEF modules together (initializes MEF and the modules)
    /// </summary>
    public class QuickRun
    {

        public CompositionContainer _container;

        [Import(typeof(IfLogger), AllowRecomposition = true, AllowDefault = false)]
        public IfLogger _objLog { get; set; }

        [Import]
        public IdoEF _objDataIO { get; set; }

        [ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<IExecute, IExecuteMetadata>> _objPlugIns;
        //public IEnumerable<Lazy<IExecute, IExecuteMetadata>> _objPlugIns { get; set; }

        string _ComputerName;
        public ScheduleTimerBase _objTimer;
        string _pluginspath;
        string _apppath;

        /// <summary>
        /// Initializes BackupAssurance.  Initializes MEF, Outputs which plug-ins were found, and registers the events
        /// </summary>
        public QuickRun()
        {

            string output;
            _ComputerName = System.Environment.MachineName;
            _pluginspath = getAssembliesFolder();
            _apppath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);


            //initialize MEF
            Compose();
            _objDataIO.addAuditLog("BackupAssurance Service Starting at " + DateTime.Now, "BackupAssurance Start", 0, 0, "Starting");
            output = string.Format("MEF Initialized.  Composition done for {0} at {1}", _ComputerName, DateTime.Now);
            _objLog.WriteLine(output);
            _objLog.WriteLine("Composition is done.  Listing Assemblies:");


            foreach (var part in _container.Catalog.Parts)
            {
                output = string.Format("Assemblies found: {0}, {1}", part.Metadata.ToString(), part.ToString());
                _objLog.WriteLine(output);
                part.ExportDefinitions.ToList().ForEach(e => _objLog.WriteLine(e.ToString()));
            }

            _objLog.WriteLine("Plugins found:");
            foreach (Lazy<IExecute, IExecuteMetadata> plugin in _objPlugIns)
            {
                _objLog.WriteLine(plugin.Metadata.PlugInType);
                _objLog.WriteLine(plugin.Value.ToString());
            }


            _objLog.WriteLine("Setting up the Timer and events.");
            _objTimer = new ScheduleTimerBase(_ComputerName);
            _container.SatisfyImportsOnce(_objTimer);
            _objTimer.UpdateNextToRunFieldEvent += new NextJobToRunDelegate(_objTimerControl_UpdateNextToRunEvent);
            _objTimer.ErrorEvent += new ExceptionDelegate(_objTimerControl_ErrorEvent);
            _objTimer.ElapsedEvent += new TimerElapsedDelegate(_objTimerControl_ElapsedEvent);
            _objTimer.RunNowEvent += new RunNowDelegate(_objTimerControl_UpdateRunNowFlag);
        }

        /// <summary>
        /// Called to send emails using default settings.  Only needs the email title and the email body.
        /// Default settings are read from the app.config file.
        /// </summary>
        private void sendemail(string strText, string strTitle)
        {
            string Mail_Server;
            string Mail_Port;
            string Mail_From;
            string Mail_To;
            string[] aMail_To;

            string Mail_Subject;
            string Mail_Login;
            string Mail_Password;

            try
            {
                Mail_Server = ConfigurationManager.AppSettings["Mail_Server"];
                Mail_Port = ConfigurationManager.AppSettings["Mail_Port"];
                Mail_From = ConfigurationManager.AppSettings["Mail_From"];
                Mail_To = ConfigurationManager.AppSettings["Mail_To"];
                Mail_Subject = ConfigurationManager.AppSettings["Mail_Subject"];
                Mail_Subject = Mail_Subject + " " + strTitle;
                Mail_Login = ConfigurationManager.AppSettings["Mail_Login"];
                Mail_Password = ConfigurationManager.AppSettings["Mail_Password"];
            }
            catch (Exception ex)
            {
                _objLog.WriteError(0, "Error reading  email setting from the BackupAssurance.config file" + Environment.NewLine + ex.Message);
                return;
            }

            try
            {

                MailMessage mail = new MailMessage();
                SmtpClient SmtpServer = new SmtpClient(Mail_Server);
                aMail_To = Mail_To.Split(';');
                mail.From = new MailAddress(Mail_From);

                foreach (string toAddress in aMail_To)
                {
                    mail.To.Add(toAddress);
                }

                mail.Subject = Mail_Subject;
                mail.Body = strText;

                SmtpServer.Port = int.Parse(Mail_Port);
                SmtpServer.Credentials = new System.Net.NetworkCredential(Mail_Login, Mail_Password);
                SmtpServer.EnableSsl = false;

                SmtpServer.Send(mail);
                _objLog.WriteLine("email Sent Mail_To:" + Mail_To + " Date:" + DateTime.Now);
                _objLog.WriteLine("");
            }
            catch (Exception ex)
            {
                _objLog.WriteError(0, "Error sending email." + Environment.NewLine + ex.Message);
                return;
            }

        }

        /// <summary>
        /// Called when the windows service is due to start (automatically when the computer boots), manually from the Services console,
        /// or when the ProjectStartsHere is run.
        /// No MEF initialization occurs here.
        /// This method reads all the jobs from the dbatabase table and schedules the next available job ton run.
        /// </summary>
        public void OnStart()
        {

            string output;
            output = string.Format("[BackupAssurance] Service Starting up on [{0}] at [{1}] ", _ComputerName, DateTime.Now);
            _objLog.WriteLine(output);

            output = string.Format("Plug-Ins Path: [{0}] ", _pluginspath);
            _objLog.WriteLine(output);

            output = string.Format("Assembly and Configuration File Path: [{0}] ", _apppath);
            _objLog.WriteLine(output);

            _objLog.WriteLine("Database Connection String:");
            string ConnectionString = ConfigurationManager.AppSettings["FileDeleteDB"];
            ConnectionStringSettingsCollection settings = ConfigurationManager.ConnectionStrings;
            if (settings != null)
            {
                foreach (ConnectionStringSettings cs in settings)
                {
                    output = string.Format("Connection: {0}, {1}, {2}", cs.Name, cs.ProviderName, cs.ConnectionString);
                    _objLog.WriteLine(output);
                }
            }

            //get the jobs from the database
            _objLog.WriteLine("Retrieving Active Jobs from the database:");
            List<pcJobAction> jobsfromdb = _objDataIO.getJobActions(_ComputerName);
            List<pcPlugInValue> pluginsfromdb = _objDataIO.GetAllplugins(_ComputerName);

            string output2;
            foreach (pcJobAction item in jobsfromdb)
            {
                output2 = String.Format("read from DB: {0}", item.name);
                _objLog.WriteLine(output2);
            }

            _objTimer.StopTimer();
            _objTimer.ClearJobs();

            // load all jobs into the timer
            _objLog.WriteLine("Load Jobs into the Timer:");
            _objTimer.AddAllTimedEvent(jobsfromdb, pluginsfromdb);

            pluginsfromdb.Clear();
            jobsfromdb.Clear();
            pluginsfromdb = null;
            jobsfromdb = null;

            _objLog.WriteLine("BackupAssurance Initialization Complete.");
            _objDataIO.addAuditLog("BackupAssurance Service Started at " + DateTime.Now, "BackupAssurance Start", 0, 0, "Started Successfully");

            List<ScheduledTime> _objTimers = _objTimer.getScheduledJobs();
            foreach (var item in _objTimers)
            {
                output = String.Format("{0}, [{1}], {2}", item._JobAction.Id, item._JobAction.name, item._NextRunDateTime);
                _objLog.WriteLine(output);
            }

        }

        /// <summary>
        /// This method is called when a user stops the service manually from the Windows Services window.
        /// </summary>
        public void OnStop()
        {
            _objTimer.StopTimer();
            _objDataIO.addAuditLog("BackupAssurance Service Stopped at " + DateTime.Now, "BackupAssurance Stop", 0, 0, "Stopped Successfully");
            _objLog.WriteLine("BackupAssurance Service Stopped at :" + DateTime.Now);

            _container.Dispose();
            _container = null;
            _objLog = null;
            _objDataIO = null;
            _objPlugIns = null;

        }

        /// <summary>
        /// Gets the location to all the DLL files from app.config
        /// </summary>
        private string getAssembliesFolder()
        {
            string path;
            //get the location to the dll files
            try
            {
                NameValueCollection appSettings = ConfigurationManager.AppSettings;
                path = appSettings.Get("AssembliesFolder");
                //path = ConfigurationManager.AppSettings["AssembliesFolder"];
            }
            catch (Exception ex)
            {
                throw new Exception("AssembliesFolder not found in the app.config file" + Environment.NewLine + ex.Message);
            }

            return path;
        }
        /// <summary>
        /// Helper Method.  It combines DLLs read from the file system, to those available from the visual c# project
        /// to create a listing of all functionality avaialble to the application.
        /// Once MEF has composed all the available DLLs, it can import them into classes needed the dynamic real-time functionality.
        /// </summary>
        private void Compose()
        {

            AssemblyCatalog catalogAssembly = new AssemblyCatalog(Assembly.GetExecutingAssembly());

            if ((_pluginspath != null) && (_pluginspath != "") && Directory.Exists(_pluginspath))
            {
                DirectoryCatalog catalogFolder = new DirectoryCatalog(_pluginspath);
                AggregateCatalog catalogAggregate = new AggregateCatalog(catalogAssembly, catalogFolder);
                _container = new CompositionContainer(catalogAggregate);
            }
            else
            {
                _container = new CompositionContainer(catalogAssembly);
            }
            _container.ComposeParts(this);
        }

        /// <summary>
        /// This method is called every 30-90 minutes (configurable) to re-read all the jobs from the database
        /// and schedule the next job.
        /// </summary>
        private void servicereset(ScheduledEventArg Args, pcPlugInValue objJobRequested)
        {

            List<pcJobAction> jobsfromdb;

            //drop unused objects
            _objTimer.StopTimer();
            _objLog.WriteLine("---------------------------------------------------------------------------->");
            _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Run", 0, Args.EventTime._JobAction.Id, objJobRequested.TypePlugIn + " " + objJobRequested.Runversion.ToString());

            //get the jobs from the database
            _objLog.WriteLine("Retrieving Active Jobs from the Database and Timer for comparison:");

            //get all the active jobs for this computer from the database 
            jobsfromdb = _objDataIO.getJobActions(_ComputerName);

            //compare the jobs read from the database with the jobs from the timer; look for changes.
            Boolean wasupdated = _objTimer.compareDBjobToTimerjob(jobsfromdb);
            if (wasupdated != true)
            {
                _objLog.WriteLine("No new changes found during the BackupAssurance refresh.");
            }
            //restart
            _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Complete", 0, Args.EventTime._JobAction.Id, "");
            _objLog.WriteLine("----------------------------------------------------------------------------<");
        }

        /// <summary>
        /// This method is called by the timer, when a job has been scheduled and the "next runtime" field needs to be udpated in the database with the new date/time
        /// This method is responsible for keeping the "next runtime" field updated, which can be used by the user
        /// to query the database table and know when each job will run.
        /// </summary>
        public void _objTimerControl_UpdateNextToRunEvent(object sender, ScheduledEventArg Args)
        {
            _objLog.WriteLine("_objTimerControl_NextToRunEvent");
            //update the table (next to run)
            //we can update the next time the job will run
            string results = _objDataIO.updateJobActionNextRunTime(Args.EventTime._JobAction, Args.EventTime._NextRunDateTime);

            if (results != "")
            {
                _objLog.WriteError(0, results);
            }
        }

        /// <summary>
        /// This method is called by the timer, when a job that was scheduled to run "right now" was executed, and now its time to turn off the flag
        /// so that it wont keep on running.
        /// </summary>
        public void _objTimerControl_UpdateRunNowFlag(object sender, ScheduledEventArg Args)
        {
            string output = String.Format("Turning off  RunNow database flag , so that job id {0} wont execute again.", Args.EventTime._JobAction.Id);
            _objLog.WriteLine(output);
            //if the job was one of those with the RUNNOW flag, then turn off the flag so its not kicked off again
            string results = _objDataIO.updateRunNowFlag(Args.EventTime._JobAction, DateTime.Now);

            if (results != "")
            {
                _objLog.WriteError(0, results);
            }
        }

        /// <summary>
        /// This method is called by the timer, when an error is found during the execution of a plug-in
        /// Currently this method doesn't do anything
        /// </summary>
        public void _objTimerControl_ErrorEvent(object sender, ExceptionEventArgs e)
        {
            _objLog.WriteLine("_objTimerControl_ErrorEvent");
            //oops an error
        }

        /// <summary>
        /// This method is called by the timer, when a job is due to run.
        /// Upon calling the method, the service will get all available plugin information
        /// The method then loops through all the jobs needing to execute (not just the one that triggered the alert
        /// in case that there are more than one job scheduled for the same date/time
        /// For each job that needs to be executed, it confirms if there is a plug-in on the file system
        /// matching to the plug-in name read from the database
        /// If the dll is found, then the [EXECUTE] method of the plug in is called, so that the plug in can do its job
        /// Any data applicable to the plug-in, that was read from the database, is passed to the plugin in the execute call.
        /// </summary>
        public void _objTimerControl_ElapsedEvent(object sender, ScheduledEventArg Args)
        {

            StringBuilder objFilePlugin = new StringBuilder();
            //_objTimer.StopTimer();
            bool wasPluginFound = false;
            objFilePlugin.AppendLine(_pluginspath);
            _objLog.WriteLine("_objTimerControl_ElapsedEvent");
            //event elapsed
            string strErrorMessage;
            //we can update that the event is running

            List<pcPlugInValue> inputdata = null;
            try
            {
                inputdata = _objDataIO.Getplugins(Args.EventTime._JobAction.Id);
            }
            catch (Exception exa)
            {
                strErrorMessage = exa.Message + Environment.NewLine + exa.StackTrace;

                try
                {
                    errormessagewrapper(exa, Args, new pcPlugInValue(), objFilePlugin.ToString());
                }
                catch (Exception)
                {
                    _objLog.WriteError(0, "error retrieving jobs for this event.");
                }

                return;
            }

            string outputdata;
            string output;
            //based on the job that needs to run,
            //I need to pull the name of all the plug-ins, ordered by execution
            //then I need to execute each one (in order)
            //****** each job needs the JA data, the schedule, and the data for the plugin
            //by order of plugin execution, I need to launch each one
            try
            {
                _objDataIO.updateJobActionLastRun(Args.EventTime._JobAction, DateTime.Now);
            }
            catch (Exception ext)
            {
                strErrorMessage = ext.Message + Environment.NewLine + ext.StackTrace;

                try
                {
                    errormessagewrapper(ext, Args, new pcPlugInValue(), objFilePlugin.ToString());
                }
                catch (Exception)
                {
                    _objLog.WriteError(0, "error updating last run job event.");
                }

                return;
            }

            try
            {
                _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Start", 0, Args.EventTime._JobAction.Id, "");
            }
            catch (Exception exr)
            {
                strErrorMessage = exr.Message + Environment.NewLine + exr.StackTrace;

                try
                {
                    errormessagewrapper(exr, Args, new pcPlugInValue(), objFilePlugin.ToString());
                }
                catch (Exception)
                {
                    _objLog.WriteError(0, "error adding an entry into the audit log to indicate the job started.");
                }

                return;
            }



            //call the required plugin
            for (int i = 0; i < inputdata.Count; i++)
            {
                if (inputdata[i].name == "Refresh")
                {
                    _objLog.WriteLine("");
                    output = string.Format("Firing Refresh Job {0}", DateTime.Now);
                    _objLog.WriteLine(output);
                    servicereset(Args, inputdata[i]);
                }
                else
                {
                    wasPluginFound = false;
                    //call the required plugin
                    foreach (Lazy<IExecute, IExecuteMetadata> plugin in _objPlugIns)
                    {
                        objFilePlugin.AppendLine(plugin.Metadata.PlugInType + " v" + plugin.Metadata.Version);

                        if (plugin.Metadata.PlugInType == inputdata[i].TypePlugIn && plugin.Metadata.Version == inputdata[i].Runversion)
                        {
                            wasPluginFound = true;

                            //call the plug in
                            try
                            {
                                _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Run", 0, Args.EventTime._JobAction.Id, inputdata[i].TypePlugIn + " " + inputdata[i].Runversion.ToString());
                                outputdata = plugin.Value.Execute(Args.EventTime._JobAction, inputdata[i]);

                                if (outputdata == "")
                                {
                                    outputdata = "No Result Text from Plugin: " + inputdata[i].TypePlugIn + " " + inputdata[i].Runversion.ToString() + " PlugIn ID:" + inputdata[i].Id.ToString();
                                }

                                output = String.Format("Job Complete. Job Name:[{0}],  Job ID: {1}, PlugIn Name:[{2}], PlugIn ID: {3}, Result: [{4}]", Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, Args.EventTime._JobAction.Id, inputdata[i].TypePlugIn + " " + inputdata[i].Runversion.ToString(), inputdata[i].Id, outputdata);
                                _objLog.WriteLine(output);
                                _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Complete", 0, Args.EventTime._JobAction.Id, outputdata);


                                if (inputdata[i].SendEmailWithResults == 1)
                                {
                                    try
                                    {
                                        sendemail(output, inputdata[i].name);
                                    }
                                    catch (Exception exi)
                                    {
                                        strErrorMessage = exi.Message + Environment.NewLine + exi.StackTrace;

                                        try
                                        {
                                            errormessagewrapper(exi, Args, inputdata[i], objFilePlugin.ToString());
                                        }
                                        catch (Exception)
                                        {
                                            _objLog.WriteError(0, "Error sending email.");
                                        }

                                    }

                                }
                            }
                            catch (Exception ex)
                            {
                                strErrorMessage = ex.Message + Environment.NewLine + ex.StackTrace;

                                try
                                {
                                    errormessagewrapper(ex, Args, inputdata[i], objFilePlugin.ToString());
                                }
                                catch (Exception)
                                {
                                    _objLog.WriteError(0, "Plugin Failed.  Printing Reason also failed.");
                                }


                                switch (inputdata[i].TypeOnFailure)
                                {
                                    case Constants.JOB_FAILURE_CONTINUE:
                                        break;
                                    case Constants.JOB_FAILURE_STOP:
                                        sendemail("Job Failed to Run.  Job Name: " + Args.EventTime._JobAction.name + " Job ID: " + Args.EventTime._JobAction.Id.ToString() + " PlugIn Name:" + inputdata[i].name + " PlugIn ID: " + inputdata[i].Id.ToString() + Environment.NewLine + strErrorMessage, inputdata[i].name);
                                        return;
                                    case Constants.JOB_FAILURE_RETRY:
                                        try
                                        {
                                            Thread.Sleep(10000);
                                            outputdata = plugin.Value.Execute(Args.EventTime._JobAction, inputdata[i]);
                                            _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Complete", 0, Args.EventTime._JobAction.Id, outputdata);
                                        }
                                        catch (Exception e)
                                        {
                                            errormessagewrapper(e, Args, inputdata[i], objFilePlugin.ToString());

                                        }
                                        return;
                                    default:
                                        break;
                                }

                            }

                        }
                    }
                    if (wasPluginFound == false)
                    {
                        errormessagewrapper(new Exception("A plugin matching the specification was not found."), Args, inputdata[i], objFilePlugin.ToString());
                    }
                }

            }

            //_objTimer.STBStart();
        }

        private void errormessagewrapper(Exception e, ScheduledEventArg Args, pcPlugInValue objJobRequested, string strOtherInfo)
        {
            string output;
            output = String.Format("_objTimerControl_ElapsedEvent " + Environment.NewLine + " Date/Time: {0}, Server Name: {1}, Job Action ID: {2}, Plugin ID: {3}, Plugin Name: {4}, Plugin Version: {5}, Other: {6} " + Environment.NewLine + " Error Message: {7} " + Environment.NewLine + "Stack Trace: {8}", DateTime.Now, Args.EventTime._JobAction.ServerName, Args.EventTime._JobAction.Id, objJobRequested.Id, objJobRequested.name, objJobRequested.Runversion, strOtherInfo, e.Message, e.StackTrace);
            _objDataIO.addAuditLog(Args.EventTime._JobAction.ServerName + " " + Args.EventTime._JobAction.name, "Job Error", 0, Args.EventTime._JobAction.Id, output);
            _objLog.WriteError(1, output);
        }

        public Dictionary<string, string> ConvertXMLtoDictionary(string inputtext)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            inputtext = "<data>" + inputtext + "</data>";

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(inputtext);

            XmlNodeList resources = xml.SelectNodes("data/resource");
            //SortedDictionary<string, string> dictionary = new SortedDictionary<string, string>(); 

            foreach (XmlNode node in resources)
            {
                dic.Add(node.Attributes["key"].Value, node.InnerText);
            }

            return dic;
        }
    }
}
